/* eslint-disable @typescript-eslint/no-var-requires */
// NOTE: we will generate preload types from this file

import { ipcRenderer } from 'electron';

import type { MainIPCEventMap, MainIPCHandlerMap } from '../../constraints';

type WithoutFirstParameter<T> = T extends (_: any, ...args: infer P) => infer R
  ? (...args: P) => R
  : T;

type HandlersMap<N extends keyof MainIPCHandlerMap> = {
  [K in keyof MainIPCHandlerMap[N]]: WithoutFirstParameter<
    MainIPCHandlerMap[N][K]
  >;
};

type PreloadHandlers = {
  [N in keyof MainIPCHandlerMap]: HandlersMap<N>;
};

type MainExposedMeta = {
  handlers: [namespace: string, handlerNames: string[]][];
  events: [namespace: string, eventNames: string[]][];
};

// main handlers that can be invoked from the renderer process
const apis: PreloadHandlers = (() => {
  // the following were generated by the build script
  // 1. bundle extra main/src/expose.ts entry
  // 2. use generate-main-exposed-meta.mjs to generate exposed-meta.js in dist
  //
  // we cannot directly import main/src/handlers.ts because it will be bundled into the preload bundle
  // eslint-disable-next-line @typescript-eslint/no-var-requires
  const {
    handlers: handlersMeta,
  }: MainExposedMeta = require('../main/exposed-meta');

  const all = handlersMeta.map(([namespace, functionNames]) => {
    const namespaceApis = functionNames.map(name => {
      const channel = `${namespace}:${name}`;
      return [
        name,
        (...args: any[]) => {
          return ipcRenderer.invoke(channel, ...args);
        },
      ];
    });
    return [namespace, Object.fromEntries(namespaceApis)];
  });

  return Object.fromEntries(all);
})();

// main events that can be listened to from the renderer process
const events: MainIPCEventMap = (() => {
  const {
    events: eventsMeta,
  }: MainExposedMeta = require('../main/exposed-meta');

  // NOTE: ui may try to listen to a lot of the same events, so we increase the limit...
  ipcRenderer.setMaxListeners(100);

  const all = eventsMeta.map(([namespace, eventNames]) => {
    const namespaceEvents = eventNames.map(name => {
      const channel = `${namespace}:${name}`;
      return [
        name,
        (callback: (...args: any[]) => void) => {
          const fn: (
            event: Electron.IpcRendererEvent,
            ...args: any[]
          ) => void = (_, ...args) => {
            callback(...args);
          };
          ipcRenderer.on(channel, fn);
          return () => {
            ipcRenderer.off(channel, fn);
          };
        },
      ];
    });
    return [namespace, Object.fromEntries(namespaceEvents)];
  });
  return Object.fromEntries(all);
})();

const appInfo = {
  electron: true,
};

export { apis, appInfo, events };
